namespace OpenComposite.HumanActivity
{
	using System;
	using System.Collections.Generic;
	using System.Data;
	using System.Diagnostics;
	using System.Net;
	using System.ServiceModel;
	using System.ServiceModel.Description;
	using System.ServiceProcess;
	using System.Timers;

	using OpenComposite.HumanActivity.Contracts;
	using OpenComposite.HumanActivity.Data;
	using OpenComposite.HumanActivity.Services;

	[ServiceBehavior(
		   ConcurrencyMode = ConcurrencyMode.Multiple,
		   InstanceContextMode = InstanceContextMode.Single,
		   Namespace = "http://www.theopencomposite.com/HumanActivity")]
	public partial class ServiceMain : ServiceBase, IHumanActivityDesignerService
	{
		#region Initialization

		public ServiceMain()
		{
			InitializeComponent();
		}

		#endregion Initialization

#if DEBUG

		public void StartService()
		{
			OnStart(null);
		}

		public void StopService()
		{
			OnStop();
		}

#endif

		#region Public Members

		#region Methods

		#region IHumanActivityDesignerService Members

		public void Update(Guid identifier, int hactId)
		{
			try {
				checkForNew(hactId);
			} catch ( Exception ex ) {
				throw Global.CreateAndLogHumanActivityFault("Update(hactId=" + hactId.ToString() + ")", ex);
			}
		}

		public void UpdateAll(Guid identifier)
		{
			try {
				checkForNew(-1);
			} catch ( Exception ex ) {
				throw Global.CreateAndLogHumanActivityFault("UpdateAll", ex);
			}
		}

		#endregion

		#endregion Methods

		#endregion Public Members

		#region Protected Members

		#region Methods

		protected override void OnStart(string[] args)
		{
			Global.EventLog = this.EventLog;
			try {
				startServices();
			} catch ( Exception ex ) {
				this.EventLog.WriteEntry(ex.Message, EventLogEntryType.Error);
			}
		}

		protected override void OnStop()
		{
			stopServices();
		}

		#endregion Methods

		#endregion Protected Members

		#region Private Members

		#region Fields

		private SortedList<int, DataRow> _addedRows = new SortedList<int, DataRow>();
		private SortedList<int, ServiceHost> _hostsHAFormSvc = new SortedList<int, ServiceHost>();
		private SortedList<int, ServiceHost> _hostsHASvc = new SortedList<int, ServiceHost>();
		private Guid _identifier = Guid.NewGuid();
		private List<int> _lstAddedHActs = new List<int>();
		private int _port = 30000;
		private Timer _timer = null;
		private Timer _timerCheckNew = null;
		private ServiceHost _updateService = null;
		private ServiceHost _dataService = null;
		private ServiceHost _attachmentService = null;

		#endregion Fields

		#region Event Handlers

		private void _timerCheckNew_Elapsed(object sender, ElapsedEventArgs e)
		{
			checkForNew();
		}

		private void _timer_Elapsed(object sender, ElapsedEventArgs e)
		{
			checkForNew();
		}

		private void host_Closed(object sender, EventArgs e)
		{
			Trace.WriteLine("host_Closed");
		}

		private void host_Faulted(object sender, EventArgs e)
		{
			Trace.WriteLine("host_Faulted");
		}

		private void host_Opened(object sender, EventArgs e)
		{
			ServiceHost sh = sender as ServiceHost;
			if ( sh != null ) {
				string msg = "Service Host Opened: Type=" + sh.SingletonInstance.GetType().Name;
				msg += Environment.NewLine + "Addresses: " + Environment.NewLine;
				foreach ( var dispatcher in sh.ChannelDispatchers ) {
					msg += dispatcher.Listener.Uri.ToString() + Environment.NewLine;
				}
				msg += "END" + Environment.NewLine;
				Trace.WriteLine(msg);
			} else {
				Trace.WriteLine("host_Opened");
			}
		}

		private void host_UnknownMessageReceived(object sender, UnknownMessageReceivedEventArgs e)
		{
			Trace.WriteLine("host_UnknownMessageReceived");
		}

		#endregion Event Handlers

		#region Methods

		private void addAttachmentServiceHost()
		{
			Type typeContract = typeof(IAttachmentService);

			string urltemplate = "<TYPE>://<SERVER>:<PORT>/opencompositeservices/attachments";
			string sUrlNetTcp = urltemplate
				.Replace("<TYPE>", "net.tcp")
				.Replace("<SERVER>", Dns.GetHostName())
				.Replace("<PORT>", _port.ToString());
			string sUrlHttp = urltemplate
				.Replace("<TYPE>", "http")
				.Replace("<SERVER>", Dns.GetHostName())
				.Replace("<PORT>", ( (int)( _port + 1 ) ).ToString());

			Uri uriNETTCP = new Uri(sUrlNetTcp);
			Uri uriWSHTTP = new Uri(sUrlHttp);

			_attachmentService = new ServiceHost(new AttachmentService());
			_attachmentService.Opened += new EventHandler(host_Opened);
			_attachmentService.Closed += new EventHandler(host_Closed);
			_attachmentService.Faulted += new EventHandler(host_Faulted);
			_attachmentService.UnknownMessageReceived += new EventHandler<UnknownMessageReceivedEventArgs>(host_UnknownMessageReceived);

			ServiceDebugBehavior debugBehavior = _attachmentService.Description.Behaviors.Find<ServiceDebugBehavior>();
			if ( debugBehavior != null ) {
				debugBehavior.IncludeExceptionDetailInFaults = true;
			}

			WSHttpBinding wsbinding = new WSHttpBinding("WSHttpBinding_IAttachmentService");
			wsbinding.Namespace = _attachmentService.Description.Namespace;
			NetTcpBinding tcpBinding = new NetTcpBinding("NetTcpBinding_IAttachmentService");
			tcpBinding.Namespace = _attachmentService.Description.Namespace;

			ServiceEndpoint se0 = _attachmentService.AddServiceEndpoint(typeContract, wsbinding, uriWSHTTP);
			ServiceEndpoint se1 = _attachmentService.AddServiceEndpoint(typeContract, tcpBinding, uriNETTCP);

			ServiceMetadataBehavior metaBehavior;
			metaBehavior = _attachmentService.Description.Behaviors.Find<ServiceMetadataBehavior>();
			if ( metaBehavior == null ) {
				metaBehavior = new ServiceMetadataBehavior();
				metaBehavior.HttpGetEnabled = true;
				metaBehavior.HttpGetUrl = uriWSHTTP;
				_attachmentService.Description.Behaviors.Add(metaBehavior);
			}
			_attachmentService.Open();

			this.EventLog.WriteEntry(
				String.Format("Attachment Service successfully added. ({0}, {1})", uriWSHTTP, uriNETTCP),
				EventLogEntryType.Information);
		}
		private void addDataServiceHost()
		{
			Type typeContract = typeof(IHumanActivityDataService);

			string urltemplate = "<TYPE>://<SERVER>:<PORT>/opencompositeservices/data";
			string sUrlNetTcp = urltemplate
				.Replace("<TYPE>", "net.tcp")
				.Replace("<SERVER>", Dns.GetHostName())
				.Replace("<PORT>", _port.ToString());
			string sUrlHttp = urltemplate
				.Replace("<TYPE>", "http")
				.Replace("<SERVER>", Dns.GetHostName())
				.Replace("<PORT>", ( (int)( _port + 1 ) ).ToString());

			Uri uriNETTCP = new Uri(sUrlNetTcp);
			Uri uriWSHTTP = new Uri(sUrlHttp);

			_dataService = new ServiceHost(new HumanActivityDataService());
			_dataService.Opened += new EventHandler(host_Opened);
			_dataService.Closed += new EventHandler(host_Closed);
			_dataService.Faulted += new EventHandler(host_Faulted);
			_dataService.UnknownMessageReceived += new EventHandler<UnknownMessageReceivedEventArgs>(host_UnknownMessageReceived);

			ServiceDebugBehavior debugBehavior = _dataService.Description.Behaviors.Find<ServiceDebugBehavior>();
			if ( debugBehavior != null ) {
				debugBehavior.IncludeExceptionDetailInFaults = true;
			}

			WSHttpBinding wsbinding = new WSHttpBinding("WSHttpBinding_IHumanActivityDataService");
			wsbinding.Namespace = _dataService.Description.Namespace;
			NetTcpBinding tcpBinding = new NetTcpBinding("NetTcpBinding_IHumanActivityDataService");
			tcpBinding.Namespace = _dataService.Description.Namespace;

			ServiceEndpoint se0 = _dataService.AddServiceEndpoint(typeContract, wsbinding, uriWSHTTP);
			ServiceEndpoint se1 = _dataService.AddServiceEndpoint(typeContract, tcpBinding, uriNETTCP);

			ServiceMetadataBehavior metaBehavior;
			metaBehavior = _dataService.Description.Behaviors.Find<ServiceMetadataBehavior>();
			if ( metaBehavior == null ) {
				metaBehavior = new ServiceMetadataBehavior();
				metaBehavior.HttpGetEnabled = true;
				metaBehavior.HttpGetUrl = uriWSHTTP;
				_dataService.Description.Behaviors.Add(metaBehavior);
			}
			_dataService.Open();

			this.EventLog.WriteEntry(
				String.Format("Data Service successfully added. ({0}, {1})", uriWSHTTP, uriNETTCP),
				EventLogEntryType.Information);
		}

		private void addService(DataRow row)
		{
			int hactID = -1;
			string serviceName = "unknown";
			try {
				hactID = (int)row["hact_id"];
				serviceName = row["hact_nm"].ToString();

				if ( _addedRows.ContainsValue(row) ) return; // EXIT
				if ( _addedRows.ContainsKey(hactID) ) {
					DataRow oldRow = _addedRows[hactID];
					try {
						if ( oldRow["hact_form_dll"] == row["hact_form_dll"] &&
							oldRow["hact_form_asm_nm"] == row["hact_form_asm_nm"] &&
							oldRow["hact_form_nm"] == row["hact_form_nm"] &&
							oldRow["hact_schema_nm"] == row["hact_schema_nm"] &&
							oldRow["hact_schema"] == row["hact_schema"] &&
							oldRow["hact_url"] == row["hact_url"] ) {
							// no update for this human activity
							return;	// EXIT
						} else {
							_addedRows.Remove(hactID);
						}
					} catch { }
				}

				addServiceHost(new Services.HumanActivityService(row, this.EventLog),
							   typeof(IHumanActivityService),
							   serviceName, null, true,
							   "NetTcpBinding_IHumanActivityService",
							   "WSDualHttpBinding_IHumanActivityService",
							   row);
				addServiceHost(new Services.HumanActivityFormService(row, this.EventLog),
							   typeof(Contracts.IHumanActivityFormService),
							   serviceName, "Form", false,
							   "NetTcpBinding_IHumanActivityFormService",
							   "WSDualHttpBinding_IHumanActivityFormService",
							   row);

				this.EventLog.WriteEntry(
					string.Format("Human Activity Service '{0}' successfully added.(MasterUrl={1})", serviceName, row["hact_url"].ToString()),
					EventLogEntryType.Information);

				_addedRows.Add(hactID, row);
			} catch ( Exception ex ) {
				Trace.WriteLine(ex.ToString());
				this.EventLog.WriteEntry(
					String.Format("Add Service Error: {0}({1}:{2})", ex.Message, serviceName, hactID),
					EventLogEntryType.Error);
			}
		}

		private void addServiceHost(object service, Type typeContract, string serviceName, string typeName,
			bool isMainService, string nettcpConfig, string httpConfig, DataRow row)
		{
			Uri uriNETTCP = null;
			Uri uriWSHTTP = null;

			int hactID = (int)row["hact_id"];
			if ( service is HumanActivityFormService ) {
				if ( _hostsHAFormSvc.ContainsKey(hactID) ) {
					ServiceHost svcHost = _hostsHAFormSvc[hactID];
					try {
						svcHost.Close();
					} catch ( Exception ex ) {
						this.EventLog.WriteEntry(
							String.Format("Close HumanActivityFormService Service Host Error: {0}", ex.Message),
							EventLogEntryType.Error);
					}
					_hostsHAFormSvc.Remove(hactID);
				}
			} else {
				if ( _hostsHASvc.ContainsKey(hactID) ) {
					ServiceHost svcHost = _hostsHASvc[hactID];
					try {
						svcHost.Close();
					} catch ( Exception ex ) {
						this.EventLog.WriteEntry(
							String.Format("Close HumanActivityService Service Host Error: {0}", ex.Message),
							EventLogEntryType.Error);
					}
					_hostsHASvc.Remove(hactID);
				}
			}
			if ( row.IsNull("hact_url_template") ) return;

			string urltemplate = (string)row["hact_url_template"];
			// urltemplate = "<TYPE>://<SERVER>:<PORT>/orgname/domainname/hactname"
			string sUrlNetTcp = urltemplate.Replace("<TYPE>", "net.tcp")
				.Replace("<SERVER>", Dns.GetHostName()).Replace("<PORT>", _port.ToString());
			string sUrlHttp = urltemplate.Replace("<TYPE>", "http")
				.Replace("<SERVER>", Dns.GetHostName()).Replace("<PORT>", ( (int)( _port + 1 ) ).ToString());

			Global.DataAccess.SetHumanActivityPort(_port);

			if ( string.IsNullOrEmpty(typeName) ) {
				// update the service url in the repository datebase
				Global.DataAccess.SetHumanActivityURL(hactID, sUrlNetTcp);

				uriNETTCP = new Uri(sUrlNetTcp);
				uriWSHTTP = new Uri(sUrlHttp);
			} else {
				sUrlNetTcp += "/" + typeName;
				sUrlHttp += "/" + typeName;
				uriNETTCP = new Uri(sUrlNetTcp);
				uriWSHTTP = new Uri(sUrlHttp);
			}
			ServiceHost host = new ServiceHost(service);
			host.Opened += new EventHandler(host_Opened);
			host.Closed += new EventHandler(host_Closed);
			host.Faulted += new EventHandler(host_Faulted);
			host.UnknownMessageReceived += new EventHandler<UnknownMessageReceivedEventArgs>(host_UnknownMessageReceived);

			ServiceDebugBehavior debugBehavior = host.Description.Behaviors.Find<ServiceDebugBehavior>();
			if ( debugBehavior != null ) {
				debugBehavior.IncludeExceptionDetailInFaults = true;
			}

			WSDualHttpBinding wsbinding = new WSDualHttpBinding(httpConfig);
			wsbinding.Namespace = host.Description.Namespace;
			NetTcpBinding tcpBinding = new NetTcpBinding(nettcpConfig);
			tcpBinding.Namespace = host.Description.Namespace;

			ServiceEndpoint se0 = host.AddServiceEndpoint(typeContract, wsbinding, uriWSHTTP);
			ServiceEndpoint se1 = host.AddServiceEndpoint(typeContract, tcpBinding, uriNETTCP);

			ServiceMetadataBehavior metaBehavior;
			metaBehavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>();
			if ( metaBehavior == null ) {
				metaBehavior = new ServiceMetadataBehavior();
				metaBehavior.HttpGetEnabled = true;
				metaBehavior.HttpGetUrl = uriWSHTTP;
				if ( isMainService ) {
					metaBehavior.MetadataExporter = (MetadataExporter)service;
				}
				host.Description.Behaviors.Add(metaBehavior);
			}
			host.Open();

			if ( service is HumanActivityFormService ) {
				_hostsHAFormSvc.Add(hactID, host);
			} else {
				_hostsHASvc.Add(hactID, host);
			}
		}

		private void addUpdateService()
		{
			try {
				string sUrlHttp = string.Format("http://{0}:{1}/HumanActivityUpdate", Dns.GetHostName(), _port + 2);
				Global.DataAccess.SetHumanActivityUpdateUrl(_identifier, sUrlHttp);
				Uri uriWSHTTP = new Uri(sUrlHttp);

				_updateService = new ServiceHost(this);

				_updateService.Opened += new EventHandler(host_Opened);
				_updateService.Closed += new EventHandler(host_Closed);
				_updateService.Faulted += new EventHandler(host_Faulted);
				_updateService.UnknownMessageReceived += new EventHandler<UnknownMessageReceivedEventArgs>(host_UnknownMessageReceived);

				ServiceDebugBehavior debugBehavior = _updateService.Description.Behaviors.Find<ServiceDebugBehavior>();
				if ( debugBehavior != null ) {
					debugBehavior.IncludeExceptionDetailInFaults = true;
				}

				ServiceEndpoint se0 = _updateService.AddServiceEndpoint(
					typeof(IHumanActivityDesignerService),
					new WSHttpBinding(), uriWSHTTP);

				ServiceMetadataBehavior metaBehavior;
				metaBehavior = _updateService.Description.Behaviors.Find<ServiceMetadataBehavior>();
				if ( metaBehavior == null ) {
					metaBehavior = new ServiceMetadataBehavior();
					metaBehavior.HttpGetEnabled = true;
					metaBehavior.HttpGetUrl = uriWSHTTP;

					_updateService.Description.Behaviors.Add(metaBehavior);
				}
				_updateService.Open();

				this.EventLog.WriteEntry(
					String.Format("Update Service successfully added. ({0})", uriWSHTTP),
					EventLogEntryType.Information);
			} catch ( Exception ex ) {
				Trace.WriteLine(ex.ToString());
				this.EventLog.WriteEntry(
					String.Format("Add Update Service Error: {0}", ex.Message),
					EventLogEntryType.Error);
			}
		}

		private void checkForNew(int ID)
		{
			if ( _timerCheckNew != null ) _timerCheckNew.Stop();

			_port = Properties.Settings.Default.Port;

			if ( ID <= 0 ) {
				//				_addedRows.Clear();
				//				stopServices(false);
				DataTable dtHA = Global.DataAccess.GetHumanActivities();
				foreach ( DataRow row in dtHA.Rows ) {
					addService(row);
				}
			} else {
				DataTable dtHA = Global.DataAccess.GetHumanActivities();
				DataRow[] rows = dtHA.Select(string.Format("hact_id = {0}", ID));
				if ( rows.Length > 0 ) {
					addService(rows[0]);
				}
			}

			int interval = 600000;
			if ( _timerCheckNew == null ) {
				_timerCheckNew = new Timer(interval);
				_timerCheckNew.AutoReset = false;
				_timerCheckNew.Elapsed += new ElapsedEventHandler(_timerCheckNew_Elapsed);
			} else {
				_timerCheckNew.Interval = interval;
			}
			_timerCheckNew.Start();
		}

		private void checkForNew()
		{
			try {
				checkForNew(-1);
			} catch ( Exception ex ) {
				Trace.WriteLine(ex.ToString());
			}
		}

		private void startServices()
		{
			try {
				// write database connection to event log (without password)
				System.Data.SqlClient.SqlConnectionStringBuilder sb =
					new System.Data.SqlClient.SqlConnectionStringBuilder(Properties.Settings.Default.ConnectionString);
				if ( !string.IsNullOrEmpty(sb.Password) ) sb.Password = "**********";
				this.EventLog.WriteEntry("Human Activity Database Connection: " + sb.ToString(), EventLogEntryType.Information);

				_port = Properties.Settings.Default.Port;
			} catch ( Exception ex ) {
				Trace.WriteLine(ex.ToString());
				this.EventLog.WriteEntry(
					"Service Initialization Error: " + ex.Message,
					EventLogEntryType.Error);
				return;
			}
			try {
				// register Attachment Service
				addAttachmentServiceHost();
				// register Data Service
				addDataServiceHost();
				// register UpdateService
				addUpdateService();
			} catch ( Exception ex ) {
				Trace.WriteLine(ex.ToString());
				this.EventLog.WriteEntry(
					"Start Service Hosting Error: " + ex.Message,
					EventLogEntryType.Error);
				return;
			}

			try {
				// register Human Activities
				DataTable dtHA = Global.DataAccess.GetHumanActivities();
				if ( dtHA == null || dtHA.Rows.Count == 0 ) {
					this.EventLog.WriteEntry("No Human Activities available. Retry in 10 minutes.", EventLogEntryType.Warning);
					if ( _timer == null ) {
						_timer = new Timer(600000);
						_timer.AutoReset = false;
						_timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
					}
					_timer.Start();
					return;	// EXIT
				}
				checkForNew();
			} catch ( Exception ex ) {
				Trace.WriteLine(ex.ToString());
				this.EventLog.WriteEntry(
					"Get Human Activities failed. Retry in one minute. Error: " + ex.Message,
					EventLogEntryType.Warning);
				if ( _timer == null ) {
					_timer = new Timer(60000);
					_timer.AutoReset = false;
					_timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
				}
				_timer.Start();
			}
		}

		private void stopServices()
		{
			stopServices(true);
		}

		private void stopServices(bool all)
		{
			if ( all ) {
				closeServiceHost(_attachmentService);
				closeServiceHost(_dataService);
				closeServiceHost(_updateService);
			}
			try {
				foreach ( ServiceHost host in _hostsHAFormSvc.Values ) {
					host.Close();
				}
				foreach ( ServiceHost host in _hostsHASvc.Values ) {
					host.Close();
				}
			} catch ( Exception ex ) {
				Trace.WriteLine(ex.ToString());
				this.EventLog.WriteEntry(ex.Message, EventLogEntryType.Error);
			} finally {
				_hostsHAFormSvc.Clear();
				_hostsHASvc.Clear();
			}
		}

		private void closeServiceHost(ServiceHost host)
		{
			try {
				host.Close();
			} catch ( Exception ex ) {
				Trace.WriteLine(ex.ToString());
				this.EventLog.WriteEntry(ex.Message, EventLogEntryType.Error);
			}
		}

		#endregion Methods

		#endregion Private Members
	}
}